#!/usr/bin/python
# coding=utf-8

import platform
import os
import sys
from typing import Tuple, List, Union, Dict


class ColorPrint(object):
    DEFAULT = 0
    BLACK = 1
    RED = 2
    GREEN = 3
    YELLOW = 4
    BLUE = 5
    WHITE = 6

    texts: List[Tuple[str, int, int]]

    def __init__(self):
        self.texts = []

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        Print.print(self)

    def append(self, text: str, foreground_color: int = DEFAULT, background_color: int = DEFAULT):
        self.texts.append((text, foreground_color, background_color))
        return self


class Print(object):
    sImpl = None
    sEnableColor = True

    DEFAULT = ColorPrint.DEFAULT
    BLACK = ColorPrint.BLACK
    RED = ColorPrint.RED
    GREEN = ColorPrint.GREEN
    YELLOW = ColorPrint.YELLOW
    BLUE = ColorPrint.BLUE
    WHITE = ColorPrint.WHITE

    @staticmethod
    def enable_color(enable: bool = True):
        Print.sEnableColor = enable

    @staticmethod
    def print(msg: Union[str, ColorPrint]):
        if isinstance(msg, ColorPrint):
            Print.sImpl.print(msg)
        else:
            sys.stdout.write(msg)
            sys.stdout.flush()

        return Print

    @staticmethod
    def print_line(msg: Union[str, ColorPrint]):
        return Print.print(msg).edl()

    @staticmethod
    def edl():
        sys.stdout.write('\n')
        sys.stdout.flush()
        return Print

    @staticmethod
    def next_line():
        sys.stdout.write('\n')
        sys.stdout.flush()
        return Print

    @staticmethod
    def return_line():
        sys.stdout.write('\r')
        sys.stdout.flush()
        return Print

    @staticmethod
    def black(msg):
        if Print.sEnableColor:
            return Print.print(ColorPrint().append(msg, Print.BLACK))
        else:
            return Print.print(msg)

    @staticmethod
    def red(msg):
        if Print.sEnableColor:
            return Print.print(ColorPrint().append(msg, Print.RED))
        else:
            return Print.print(msg)

    @staticmethod
    def green(msg):
        if Print.sEnableColor:
            return Print.print(ColorPrint().append(msg, Print.GREEN))
        else:
            return Print.print(msg)

    @staticmethod
    def blue(msg):
        if Print.sEnableColor:
            return Print.print(ColorPrint().append(msg, Print.BLUE))
        else:
            return Print.print(msg)

    @staticmethod
    def yellow(msg):
        if Print.sEnableColor:
            return Print.print(ColorPrint().append(msg, Print.YELLOW))
        else:
            return Print.print(msg)

    @staticmethod
    def white(msg):
        if Print.sEnableColor:
            return Print.print(ColorPrint().append(msg, Print.WHITE))
        else:
            return Print.print(msg)

    class PrintWindows(object):
        import ctypes

        _STD_INPUT_HANDLE = -10
        _STD_OUTPUT_HANDLE = -11
        _STD_ERROR_HANDLE = -12

        FOREGROUND_INTENSITY = 0x08
        FOREGROUND_BLUE = 0x01 | FOREGROUND_INTENSITY
        FOREGROUND_GREEN = 0x02 | FOREGROUND_INTENSITY
        FOREGROUND_RED = 0x04 | FOREGROUND_INTENSITY
        FOREGROUND_BLACK = 0x00 | FOREGROUND_INTENSITY
        FOREGROUND_YELLOW = FOREGROUND_GREEN | FOREGROUND_RED
        FOREGROUND_WHITE = FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE
        BACKGROUND_INTENSITY = 0x80
        BACKGROUND_BLUE = 0x10
        BACKGROUND_GREEN = 0x20
        BACKGROUND_RED = 0x40
        BACKGROUND_BLACK = 0x00
        BACKGROUND_YELLOW = BACKGROUND_GREEN | BACKGROUND_RED
        BACKGROUND_WHITE = BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE

        _stdOutHandle = None
        _FOREGROUND_MAP: Dict[int, Union[int, None]]
        _BACKGROUND_MAP: Dict[int, Union[int, None]]

        def __init__(self):
            self._stdOutHandle = self.ctypes.windll.kernel32.GetStdHandle(self._STD_OUTPUT_HANDLE)
            self._FOREGROUND_MAP = {
                Print.DEFAULT: 0,
                Print.BLACK: Print.PrintWindows.FOREGROUND_BLACK,
                Print.RED: Print.PrintWindows.FOREGROUND_RED,
                Print.GREEN: Print.PrintWindows.FOREGROUND_GREEN,
                Print.YELLOW: Print.PrintWindows.FOREGROUND_YELLOW,
                Print.BLUE: Print.PrintWindows.FOREGROUND_BLUE,
                Print.WHITE: Print.PrintWindows.FOREGROUND_WHITE
            }
            self._BACKGROUND_MAP = {
                Print.DEFAULT: 0,
                Print.BLACK: Print.PrintWindows.BACKGROUND_BLACK,
                Print.RED: Print.PrintWindows.BACKGROUND_RED,
                Print.GREEN: Print.PrintWindows.BACKGROUND_GREEN,
                Print.YELLOW: Print.PrintWindows.BACKGROUND_YELLOW,
                Print.BLUE: Print.PrintWindows.BACKGROUND_BLUE,
                Print.WHITE: Print.PrintWindows.BACKGROUND_WHITE
            }

        def _set_cmd_color(self, foreground, background):
            foreground = self._FOREGROUND_MAP[foreground]
            background = self._BACKGROUND_MAP[background]
            return Print.PrintWindows.ctypes.windll.kernel32.SetConsoleTextAttribute(self._stdOutHandle,
                                                                                     foreground | background)

        def _reset_cmd_color(self):
            return Print.PrintWindows.ctypes.windll.kernel32.SetConsoleTextAttribute(self._stdOutHandle, 0)

        def print(self, msg: ColorPrint):
            for text, foreground, background in msg.texts:
                self._set_cmd_color(foreground, background)
                sys.stdout.write(text)
                self._reset_cmd_color()
        
    class PrintLinux(object):
        FOREGROUND_DEFAULT = 0
        FOREGROUND_BLACK = 30
        FOREGROUND_BLUE = 34
        FOREGROUND_GREEN = 32
        FOREGROUND_RED = 31
        FOREGROUND_YELLOW = 33
        FOREGROUND_WHITE = 37

        BACKGROUND_DEFAULT = 0
        BACKGROUND_BLACK = 40
        BACKGROUND_BLUE = 44
        BACKGROUND_GREEN = 42
        BACKGROUND_RED = 41
        BACKGROUND_YELLOW = 43
        BACKGROUND_WHITE = 47

        def __init__(self):
            self._FOREGROUND_MAP = {
                Print.DEFAULT: Print.PrintLinux.FOREGROUND_DEFAULT,
                Print.BLACK: Print.PrintLinux.FOREGROUND_BLACK,
                Print.RED: Print.PrintLinux.FOREGROUND_RED,
                Print.GREEN: Print.PrintLinux.FOREGROUND_GREEN,
                Print.YELLOW: Print.PrintLinux.FOREGROUND_YELLOW,
                Print.BLUE: Print.PrintLinux.FOREGROUND_BLUE,
                Print.WHITE: Print.PrintLinux.FOREGROUND_WHITE
            }
            self._BACKGROUND_MAP = {
                Print.DEFAULT: Print.PrintLinux.BACKGROUND_DEFAULT,
                Print.BLACK: Print.PrintLinux.BACKGROUND_BLACK,
                Print.RED: Print.PrintLinux.BACKGROUND_RED,
                Print.GREEN: Print.PrintLinux.BACKGROUND_GREEN,
                Print.YELLOW: Print.PrintLinux.BACKGROUND_YELLOW,
                Print.BLUE: Print.PrintLinux.BACKGROUND_BLUE,
                Print.WHITE: Print.PrintLinux.BACKGROUND_WHITE
            }

        def print(self, msg: ColorPrint):
            for text, foreground, background in msg.texts:
                foreground = self._FOREGROUND_MAP[foreground]
                background = self._BACKGROUND_MAP[background]
                if foreground == 0 and background == 0:
                    sys.stdout.write('\033[0m')
                elif foreground == 0:
                    sys.stdout.write('\033[%dm' % background)
                elif background == 0:
                    sys.stdout.write('\033[%dm' % foreground)
                else:
                    sys.stdout.write('\033[%d;%dm' % (foreground, background))
                sys.stdout.write(text)
                sys.stdout.write('\033[0m')


# init impl
if Print.sImpl is None:
    if 'Windows' in platform.system():
        if len(os.getenv('PSModulePath', '').split(os.pathsep)) >= 2:
            Print.sImpl = Print.PrintLinux()
        else:
            Print.sImpl = Print.PrintWindows()
    else:
        Print.sImpl = Print.PrintLinux()
